diff --git a/.gitignore b/.gitignore index d084c69..a73bd36 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ nb-configuration.xml # JRebel rebel-remote.xml rebel.xml +*.log +logs/ diff --git a/src/main/java/org/flossware/diskwipe/CleanDisk.java b/src/main/java/org/flossware/diskwipe/CleanDisk.java index 7056ce2..4e3d7f1 100644 --- a/src/main/java/org/flossware/diskwipe/CleanDisk.java +++ b/src/main/java/org/flossware/diskwipe/CleanDisk.java @@ -46,6 +46,33 @@ * @author Scot P. Floess */ public class CleanDisk { + /** + * Immutable set of filesystem paths that are considered unsafe targets for disk wiping operations. + * Any wipe request targeting one of these paths, or a subdirectory beneath one, is rejected + * by {@link #validateSafeDirectory(String)} with an {@link IllegalArgumentException}. + * + *

The set includes critical system directories for both Unix-like operating systems + * ({@code /}, {@code /bin}, {@code /boot}, {@code /dev}, {@code /etc}, {@code /lib}, + * {@code /lib64}, {@code /proc}, {@code /root}, {@code /sbin}, {@code /sys}, {@code /usr}, + * {@code /var}, {@code /home}) and macOS/Windows ({@code /Users}, {@code C:\}, + * {@code C:\Windows}, {@code C:\Program Files}).

+ * + *

WARNING: Removing or modifying entries in this set weakens the safety + * guard and may allow the wipe utility to destroy an operating system installation. + * Additions to this set are encouraged when deploying to environments with additional + * protected mount points.

+ * + *

Usage example (internal, via {@code validateSafeDirectory}):

+ *
{@code
+     * // This call succeeds because /tmp/wipe is not in DANGEROUS_PATHS:
+     * CleanDisk.validateSafeDirectory("/tmp/wipe");
+     *
+     * // This call throws IllegalArgumentException because "/" is dangerous:
+     * CleanDisk.validateSafeDirectory("/");
+     * }
+ * + * @see #validateSafeDirectory(String) + */ private static final Set DANGEROUS_PATHS = new HashSet<>(Arrays.asList( "/", "/bin", "/boot", "/dev", "/etc", "/lib", "/lib64", "/proc", "/root", "/sbin", "/sys", "/usr", "/var", diff --git a/src/main/java/org/flossware/diskwipe/WipeConfiguration.java b/src/main/java/org/flossware/diskwipe/WipeConfiguration.java index fb60888..aba464a 100644 --- a/src/main/java/org/flossware/diskwipe/WipeConfiguration.java +++ b/src/main/java/org/flossware/diskwipe/WipeConfiguration.java @@ -32,6 +32,29 @@ * @author Scot P. Floess */ class WipeConfiguration { + /** + * The default number of worker threads used for disk wipe operations when no explicit + * thread count is provided via {@link Builder#threadCount(int)}. + * + *

This value (4) provides a reasonable balance between parallelism and resource + * consumption on most modern multi-core systems. It is used as the initial value + * in {@link Builder} and applied whenever the caller does not override it.

+ * + *

Usage example:

+ *
+     * // Uses DEFAULT_THREAD_COUNT (4) since threadCount is not set:
+     * WipeConfiguration config = new WipeConfiguration.Builder().build();
+     * assert config.getThreadCount() == 4;
+     *
+     * // Override the default:
+     * WipeConfiguration custom = new WipeConfiguration.Builder()
+     *     .threadCount(8)
+     *     .build();
+     * 
+ * + * @see Builder#threadCount(int) + * @see #getThreadCount() + */ private static final int DEFAULT_THREAD_COUNT = 4; private static final int DEFAULT_BUFFER_SIZE = 10 * 1024 * 1024; // 10MB @@ -81,6 +104,26 @@ public boolean isSkipConfirmation() { return skipConfirmation; } + /** + * Returns a human-readable string representation of this wipe configuration, + * summarizing the thread count, buffer size in bytes, and skip confirmation flag. + * + *

The returned format is: + * {@code WipeConfiguration{threads=N, bufferSize=N bytes, skipConfirmation=true|false}}

+ * + *

Usage example:

+ *
+     * WipeConfiguration config = new WipeConfiguration.Builder()
+     *     .threadCount(8)
+     *     .bufferSize(20 * 1024 * 1024)
+     *     .build();
+     * System.out.println(config);
+     * // Output: WipeConfiguration{threads=8, bufferSize=20971520 bytes, skipConfirmation=false}
+     * 
+ * + * @return a formatted string containing the thread count, buffer size (in bytes), + * and confirmation skip flag + */ @Override public String toString() { return String.format("WipeConfiguration{threads=%d, bufferSize=%d bytes, skipConfirmation=%s}", diff --git a/src/test/java/org/flossware/diskwipe/CleanDiskTest.java b/src/test/java/org/flossware/diskwipe/CleanDiskTest.java index 4c84820..7c7d4dc 100644 --- a/src/test/java/org/flossware/diskwipe/CleanDiskTest.java +++ b/src/test/java/org/flossware/diskwipe/CleanDiskTest.java @@ -24,49 +24,114 @@ import static org.junit.jupiter.api.Assertions.*; +/** + * Unit tests for the {@link CleanDisk} application class. This test suite verifies the behavior + * of directory validation, command-line argument parsing, byte formatting, and disk wiping functionality. + * + *

Test Coverage

+ * + * + *

Usage Notes

+ * + * + *

Exit Code Convention

+ * + * + * @see CleanDisk + * @see WipeConfiguration + * @since 1.0 + */ class CleanDiskTest { + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects the root directory + * ("/") as unsafe, throwing {@link IllegalArgumentException}. + */ @Test void testValidateSafeDirectoryWithRoot() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/bin" as unsafe. + */ @Test void testValidateSafeDirectoryWithBin() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/bin")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/etc" as unsafe. + */ @Test void testValidateSafeDirectoryWithEtc() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/etc")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/usr" as unsafe. + */ @Test void testValidateSafeDirectoryWithUsr() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/usr")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/home" as unsafe. + */ @Test void testValidateSafeDirectoryWithHome() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/home")); } + /** + * Verifies that subdirectories of dangerous system directories (e.g., "/etc/subdir") are also + * rejected as unsafe. + */ @Test void testValidateSafeDirectoryWithSubdirOfDangerous() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/etc/subdir")); } + /** + * Confirms that a safe temporary directory passes validation without throwing an exception. + */ @Test void testValidateSafeDirectoryWithTempDir(@TempDir final Path tempDir) { assertDoesNotThrow(() -> CleanDisk.validateSafeDirectory(tempDir.toString())); } + /** + * Confirms that a non-existent but safe nested path (outside dangerous system directories) + * passes validation. + */ @Test void testValidateSafeDirectoryWithNonExistentSafeDir(@TempDir final Path tempDir) { final String safePath = new File(tempDir.toFile(), "safe/nested/path").getPath(); assertDoesNotThrow(() -> CleanDisk.validateSafeDirectory(safePath)); } + /** + * Verifies that a file (as opposed to a directory) is rejected by + * {@link CleanDisk#validateSafeDirectory(String)}. + */ @Test void testValidateSafeDirectoryWithFile(@TempDir final Path tempDir) throws Exception { final File file = new File(tempDir.toFile(), "testfile"); @@ -75,72 +140,115 @@ void testValidateSafeDirectoryWithFile(@TempDir final Path tempDir) throws Excep assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory(file.getPath())); } + /** + * Confirms that {@link CleanDisk#run(String[])} returns exit code 1 when invoked with no + * command-line arguments. + */ @Test void testRunWithNoArgs() { final int exitCode = CleanDisk.run(new String[]{}); assertEquals(1, exitCode, "Should return error code when no arguments provided"); } + /** + * Confirms that the long-form help flag ("--help") causes {@link CleanDisk#run(String[])} + * to return exit code 0. + */ @Test void testRunWithHelpFlag() { final int exitCode = CleanDisk.run(new String[]{"--help"}); assertEquals(0, exitCode, "Should return success code for help"); } + /** + * Confirms that the short-form help flag ("-h") causes {@link CleanDisk#run(String[])} + * to return exit code 0. + */ @Test void testRunWithHelpShortFlag() { final int exitCode = CleanDisk.run(new String[]{"-h"}); assertEquals(0, exitCode, "Should return success code for help"); } + /** + * Verifies that an unrecognized command-line option causes {@link CleanDisk#run(String[])} + * to return exit code 1. + */ @Test void testRunWithUnknownOption() { final int exitCode = CleanDisk.run(new String[]{"--unknown"}); assertEquals(1, exitCode, "Should return error code for unknown option"); } + /** + * Verifies that a non-numeric thread count value causes {@link CleanDisk#run(String[])} + * to return exit code 1. + */ @Test void testRunWithInvalidThreadCount(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-t", "invalid", tempDir.toString()}); assertEquals(1, exitCode, "Should return error code for invalid thread count"); } + /** + * Confirms that a missing value for the "-t" (thread count) option results in exit code 1. + */ @Test void testRunWithMissingThreadCountValue(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-t"}); assertEquals(1, exitCode, "Should return error code when thread count value is missing"); } + /** + * Verifies that a non-numeric buffer size value causes {@link CleanDisk#run(String[])} + * to return exit code 1. + */ @Test void testRunWithInvalidBufferSize(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-b", "invalid", tempDir.toString()}); assertEquals(1, exitCode, "Should return error code for invalid buffer size"); } + /** + * Confirms that a missing value for the "-b" (buffer size) option results in exit code 1. + */ @Test void testRunWithMissingBufferSizeValue(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-b"}); assertEquals(1, exitCode, "Should return error code when buffer size value is missing"); } + /** + * Verifies that a negative thread count is rejected with exit code 1. + */ @Test void testRunWithNegativeThreadCount(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-t", "-1", "-y", tempDir.toString()}); assertEquals(1, exitCode, "Should return error code for negative thread count"); } + /** + * Verifies that a buffer size of zero is rejected with exit code 1. + */ @Test void testRunWithZeroBufferSize(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-b", "0", "-y", tempDir.toString()}); assertEquals(1, exitCode, "Should return error code for zero buffer size"); } + /** + * Confirms that a dangerous system directory (e.g., "/etc") is rejected with exit code 1. + */ @Test void testRunWithDangerousDirectory() { final int exitCode = CleanDisk.run(new String[]{"-y", "/etc"}); assertEquals(1, exitCode, "Should return error code for dangerous directory"); } + /** + * Tests {@link CleanDisk#formatBytes(long)} with various byte counts, verifying correct + * conversion to human-readable format (B, KB, MB, GB). + */ @Test void testFormatBytes() { assertEquals("0 B", CleanDisk.formatBytes(0)); @@ -152,6 +260,10 @@ void testFormatBytes() { assertEquals("1.0 GB", CleanDisk.formatBytes(1024L * 1024 * 1024)); } + /** + * Verifies that {@link CleanDisk#wipeDir(String, WipeConfiguration)} creates worker threads + * and wipe files according to the specified configuration. + */ @Test void testWipeDirCreatesThreads(@TempDir final Path tempDir) throws Exception { final WipeConfiguration config = new WipeConfiguration.Builder() @@ -177,81 +289,130 @@ void testWipeDirCreatesThreads(@TempDir final Path tempDir) throws Exception { assertTrue(files.length > 0, "Should create wipe files"); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/var" as unsafe. + */ @Test void testValidateSafeDirectoryWithVar() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/var")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/root" as unsafe. + */ @Test void testValidateSafeDirectoryWithRootHome() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/root")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/sbin" as unsafe. + */ @Test void testValidateSafeDirectoryWithSbin() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/sbin")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/boot" as unsafe. + */ @Test void testValidateSafeDirectoryWithBoot() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/boot")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/dev" as unsafe. + */ @Test void testValidateSafeDirectoryWithDev() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/dev")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/proc" as unsafe. + */ @Test void testValidateSafeDirectoryWithProc() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/proc")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/sys" as unsafe. + */ @Test void testValidateSafeDirectoryWithSys() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/sys")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/lib" as unsafe. + */ @Test void testValidateSafeDirectoryWithLib() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/lib")); } + /** + * Verifies that {@link CleanDisk#validateSafeDirectory(String)} rejects "/lib64" as unsafe. + */ @Test void testValidateSafeDirectoryWithLib64() { assertThrows(IllegalArgumentException.class, () -> CleanDisk.validateSafeDirectory("/lib64")); } + /** + * Confirms that {@link CleanDisk#run(String[])} succeeds (exit code 0) with a valid thread + * count and a safe directory. + */ @Test void testRunWithValidThreadCount(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-t", "2", "-y", tempDir.toString()}); assertEquals(0, exitCode, "Should succeed with valid thread count"); } + /** + * Confirms that {@link CleanDisk#run(String[])} succeeds with a valid buffer size and a safe + * directory. + */ @Test void testRunWithValidBufferSize(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-b", "1024", "-y", tempDir.toString()}); assertEquals(0, exitCode, "Should succeed with valid buffer size"); } + /** + * Verifies that {@link CleanDisk#run(String[])} succeeds when multiple options (thread count + * and buffer size) are provided together. + */ @Test void testRunWithMultipleOptions(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-t", "2", "-b", "2048", "-y", tempDir.toString()}); assertEquals(0, exitCode, "Should succeed with multiple options"); } + /** + * Confirms that long-form command-line options (e.g., "--threads", "--buffer-size", "--yes") + * work correctly. + */ @Test void testRunWithLongFormOptions(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"--threads", "2", "--buffer-size", "1024", "--yes", tempDir.toString()}); assertEquals(0, exitCode, "Should succeed with long form options"); } + /** + * Tests {@link CleanDisk#formatBytes(long)} with very large byte counts (terabytes). + */ @Test void testFormatBytesWithLargeValues() { assertEquals("1.0 TB", CleanDisk.formatBytes(1024L * 1024 * 1024 * 1024)); assertEquals("1.5 TB", CleanDisk.formatBytes((long)(1.5 * 1024 * 1024 * 1024 * 1024))); } + /** + * Tests {@link CleanDisk#formatBytes(long)} with edge-case values around unit boundaries. + */ @Test void testFormatBytesWithEdgeCases() { assertEquals("1023 B", CleanDisk.formatBytes(1023)); @@ -259,18 +420,30 @@ void testFormatBytesWithEdgeCases() { assertEquals("1023.0 KB", CleanDisk.formatBytes(1024 * 1023)); } + /** + * Verifies that {@link CleanDisk#run(String[])} returns exit code 1 when options are provided + * but no target directories are specified. + */ @Test void testRunWithNoDirectories() { final int exitCode = CleanDisk.run(new String[]{"-t", "2", "-b", "1024"}); assertEquals(1, exitCode, "Should fail when no directories specified"); } + /** + * Confirms that {@link CleanDisk#run(String[])} succeeds when short-form and long-form options + * are mixed in the same command line. + */ @Test void testRunWithMixedOptions(@TempDir final Path tempDir) { final int exitCode = CleanDisk.run(new String[]{"-t", "4", "--buffer-size", "512", "-y", tempDir.toString()}); assertEquals(0, exitCode, "Should succeed with mixed short/long options"); } + /** + * Verifies that {@link CleanDisk#wipeDir(String, WipeConfiguration)} functions correctly + * with a single worker thread. + */ @Test void testWipeDirWithSingleThread(@TempDir final Path tempDir) throws Exception { final WipeConfiguration config = new WipeConfiguration.Builder() @@ -296,6 +469,10 @@ void testWipeDirWithSingleThread(@TempDir final Path tempDir) throws Exception { assertTrue(files.length > 0, "Should create wipe files with single thread"); } + /** + * Verifies that {@link CleanDisk#wipeDir(String, WipeConfiguration)} functions correctly + * with multiple worker threads (8 in this test). + */ @Test void testWipeDirWithManyThreads(@TempDir final Path tempDir) throws Exception { final WipeConfiguration config = new WipeConfiguration.Builder() @@ -321,16 +498,26 @@ void testWipeDirWithManyThreads(@TempDir final Path tempDir) throws Exception { assertTrue(files.length > 0, "Should create wipe files with many threads"); } + /** + * Confirms that an existing safe directory (created by {@link TempDir}) passes validation. + */ @Test void testValidateSafeDirectoryWithExistingDirectory(@TempDir final Path tempDir) { assertDoesNotThrow(() -> CleanDisk.validateSafeDirectory(tempDir.toString())); } + /** + * Verifies that {@link CleanDisk#printUsage()} executes without throwing an exception. + */ @Test void testPrintUsageDoesNotThrow() { assertDoesNotThrow(() -> CleanDisk.printUsage()); } + /** + * Confirms that {@link CleanDisk#formatBytes(long)} produces consistent output containing + * appropriate unit labels. + */ @Test void testFormatBytesConsistency() { final long bytes = 1536; diff --git a/src/test/java/org/flossware/diskwipe/FileWorkerTest.java b/src/test/java/org/flossware/diskwipe/FileWorkerTest.java index 781e3e0..e2a7966 100644 --- a/src/test/java/org/flossware/diskwipe/FileWorkerTest.java +++ b/src/test/java/org/flossware/diskwipe/FileWorkerTest.java @@ -24,28 +24,78 @@ import static org.junit.jupiter.api.Assertions.*; +/** + * Unit tests for {@link FileWorker}, verifying constructor validation, directory creation, + * file-writing behavior, concurrency safety, and constant definitions. + * + *

These tests use JUnit 5's {@link TempDir} extension to provide isolated temporary + * directories, ensuring no side effects on the real filesystem. Thread-based tests start + * a {@code FileWorker} on a background thread, allow it to run briefly, then interrupt + * and join to inspect the results.

+ * + *

Test categories covered:

+ * + * + *

Usage example (running from Maven):

+ *
{@code
+ * mvn test -Dtest=FileWorkerTest
+ * }
+ * + * @author Scot P. Floess + * @see FileWorker + */ class FileWorkerTest { + /** + * Verifies that constructing a {@link FileWorker} with a null directory throws + * {@link IllegalArgumentException}. + */ @Test void testConstructorWithNullDirectory() { assertThrows(IllegalArgumentException.class, () -> new FileWorker((File) null)); } + /** + * Verifies that constructing a {@link FileWorker} with a null directory and an explicit + * buffer size throws {@link IllegalArgumentException}. + */ @Test void testConstructorWithNullDirectoryAndBufferSize() { assertThrows(IllegalArgumentException.class, () -> new FileWorker((File) null, 1024)); } + /** + * Verifies that constructing a {@link FileWorker} with a buffer size of zero throws + * {@link IllegalArgumentException}. + */ @Test void testConstructorWithInvalidBufferSizeZero(@TempDir final Path tempDir) { assertThrows(IllegalArgumentException.class, () -> new FileWorker(tempDir.toFile(), 0)); } + /** + * Verifies that constructing a {@link FileWorker} with a negative buffer size throws + * {@link IllegalArgumentException}. + */ @Test void testConstructorWithInvalidBufferSizeNegative(@TempDir final Path tempDir) { assertThrows(IllegalArgumentException.class, () -> new FileWorker(tempDir.toFile(), -100)); } + /** + * Confirms that the {@link FileWorker} constructor automatically creates a non-existent + * directory when provided as a {@link File} parameter. + */ @Test void testConstructorCreatesDirectory(@TempDir final Path tempDir) { final File subDir = new File(tempDir.toFile(), "subdir"); @@ -57,6 +107,10 @@ void testConstructorCreatesDirectory(@TempDir final Path tempDir) { assertTrue(subDir.isDirectory()); } + /** + * Confirms that the {@link FileWorker} constructor accepting a {@link String} path + * automatically creates the directory if it does not exist. + */ @Test void testConstructorWithStringPath(@TempDir final Path tempDir) { final File subDir = new File(tempDir.toFile(), "stringpath"); @@ -68,6 +122,10 @@ void testConstructorWithStringPath(@TempDir final Path tempDir) { assertTrue(subDir.isDirectory()); } + /** + * Confirms that the {@link FileWorker} constructor accepting both a {@link String} path and + * an explicit buffer size creates the directory if needed. + */ @Test void testConstructorWithStringPathAndBufferSize(@TempDir final Path tempDir) { final File subDir = new File(tempDir.toFile(), "withbuffer"); @@ -79,18 +137,29 @@ void testConstructorWithStringPathAndBufferSize(@TempDir final Path tempDir) { assertTrue(subDir.isDirectory()); } + /** + * Verifies that a {@link FileWorker} can be constructed without specifying a buffer size, + * using the default buffer size defined by {@link FileWorker#DEFAULT_BUFFER_SIZE}. + */ @Test void testDefaultBufferSize(@TempDir final Path tempDir) { final FileWorker worker = new FileWorker(tempDir.toFile()); assertNotNull(worker); } + /** + * Verifies that a {@link FileWorker} can be constructed with a custom buffer size. + */ @Test void testCustomBufferSize(@TempDir final Path tempDir) { final FileWorker worker = new FileWorker(tempDir.toFile(), 1024); assertNotNull(worker); } + /** + * Verifies that executing a {@link FileWorker} on a background thread creates at least one + * wipe file in the target directory. + */ @Test void testRunCreatesFile(@TempDir final Path tempDir) throws InterruptedException { final int smallBufferSize = 1024; @@ -107,6 +176,10 @@ void testRunCreatesFile(@TempDir final Path tempDir) throws InterruptedException assertTrue(files.length > 0, "Should create at least one wipe file"); } + /** + * Confirms that the wipe file created by {@link FileWorker#run()} contains actual data + * (i.e., the file size is greater than zero). + */ @Test void testRunWritesData(@TempDir final Path tempDir) throws InterruptedException { final int smallBufferSize = 1024; @@ -125,6 +198,10 @@ void testRunWritesData(@TempDir final Path tempDir) throws InterruptedException } } + /** + * Verifies that multiple {@link FileWorker} instances can run concurrently in the same + * directory, each creating its own wipe file. + */ @Test void testMultipleWorkersInSameDirectory(@TempDir final Path tempDir) throws InterruptedException { final int workerCount = 3; @@ -149,6 +226,11 @@ void testMultipleWorkersInSameDirectory(@TempDir final Path tempDir) throws Inte assertTrue(files.length >= workerCount, "Should create at least one file per worker"); } + /** + * Confirms that files created by {@link FileWorker} follow the expected naming convention, + * with names starting with {@link FileWorker#PREFIX} ("wipe") and containing + * {@link FileWorker#SUFFIX} ("disk"). + */ @Test void testFileNamingConvention(@TempDir final Path tempDir) throws InterruptedException { final FileWorker worker = new FileWorker(tempDir.toFile(), 512); @@ -173,6 +255,10 @@ void testFileNamingConvention(@TempDir final Path tempDir) throws InterruptedExc assertTrue(foundWipeFile, "Should create file with 'wipe' prefix and 'disk' suffix"); } + /** + * Tests {@link FileWorker} behavior when the target directory is read-only. The worker should + * handle I/O failures gracefully without crashing. + */ @Test void testRunWithReadOnlyDirectory(@TempDir final Path tempDir) throws Exception { final File readOnlyDir = new File(tempDir.toFile(), "readonly"); @@ -187,6 +273,10 @@ void testRunWithReadOnlyDirectory(@TempDir final Path tempDir) throws Exception readOnlyDir.setWritable(true); } + /** + * Verifies the values of {@link FileWorker} constants: {@link FileWorker#PREFIX}, + * {@link FileWorker#SUFFIX}, and {@link FileWorker#DEFAULT_BUFFER_SIZE}. + */ @Test void testConstants() { assertEquals("wipe", FileWorker.PREFIX); @@ -194,6 +284,10 @@ void testConstants() { assertEquals(10 * 1024 * 1024, FileWorker.DEFAULT_BUFFER_SIZE); } + /** + * Confirms that a {@link FileWorker} thread completes (terminates) properly when interrupted + * after a short delay. + */ @Test void testRunCompletesSuccessfully(@TempDir final Path tempDir) throws InterruptedException { final FileWorker worker = new FileWorker(tempDir.toFile(), 512); @@ -207,6 +301,10 @@ void testRunCompletesSuccessfully(@TempDir final Path tempDir) throws Interrupte assertFalse(thread.isAlive(), "Thread should have completed"); } + /** + * Verifies that a {@link FileWorker} can be constructed with a very large buffer size + * (50 MB in this test) without throwing an exception. + */ @Test void testLargeBufferSize(@TempDir final Path tempDir) { final int largeBuffer = 50 * 1024 * 1024; // 50MB @@ -214,6 +312,10 @@ void testLargeBufferSize(@TempDir final Path tempDir) { assertNotNull(worker); } + /** + * Verifies that a {@link FileWorker} can be constructed with a very small buffer size + * (1 byte in this test) without throwing an exception. + */ @Test void testSmallBufferSize(@TempDir final Path tempDir) { final int smallBuffer = 1; // 1 byte @@ -221,6 +323,10 @@ void testSmallBufferSize(@TempDir final Path tempDir) { assertNotNull(worker); } + /** + * Verifies that multiple sequential (non-concurrent) {@link FileWorker} runs in the same + * directory produce multiple wipe files. + */ @Test void testMultipleSequentialRuns(@TempDir final Path tempDir) throws InterruptedException { for (int i = 0; i < 3; i++) { @@ -239,6 +345,10 @@ void testMultipleSequentialRuns(@TempDir final Path tempDir) throws InterruptedE assertTrue(files.length >= 3, "Should create files from multiple runs"); } + /** + * Confirms that a {@link FileWorker} with a small buffer size (100 bytes) successfully + * creates a wipe file, demonstrating basic progress reporting functionality. + */ @Test void testProgressReporting(@TempDir final Path tempDir) throws InterruptedException { final FileWorker worker = new FileWorker(tempDir.toFile(), 100); @@ -255,6 +365,10 @@ void testProgressReporting(@TempDir final Path tempDir) throws InterruptedExcept assertTrue(files.length > 0, "Should create wipe file"); } + /** + * Verifies that the {@link FileWorker} constructor automatically creates deeply nested + * directory structures (e.g., "level1/level2/level3") when they do not exist. + */ @Test void testDirectoryCreationWithNestedPath(@TempDir final Path tempDir) { final File nestedDir = new File(tempDir.toFile(), "level1/level2/level3"); @@ -266,6 +380,10 @@ void testDirectoryCreationWithNestedPath(@TempDir final Path tempDir) { assertTrue(nestedDir.isDirectory()); } + /** + * Verifies that a {@link FileWorker} can be constructed with a standard buffer size + * (1024 bytes) without throwing an exception. + */ @Test void testZeroByteBuffer(@TempDir final Path tempDir) { final FileWorker worker = new FileWorker(tempDir.toFile(), 1024); diff --git a/src/test/java/org/flossware/diskwipe/WipeConfigurationTest.java b/src/test/java/org/flossware/diskwipe/WipeConfigurationTest.java index 3993851..0937a34 100644 --- a/src/test/java/org/flossware/diskwipe/WipeConfigurationTest.java +++ b/src/test/java/org/flossware/diskwipe/WipeConfigurationTest.java @@ -20,8 +20,46 @@ import static org.junit.jupiter.api.Assertions.*; +/** + * Unit tests for {@link WipeConfiguration} and its nested {@link WipeConfiguration.Builder}, + * verifying default values, builder fluency, input validation, and string representation. + * + *

This test class exercises the following aspects of {@code WipeConfiguration}:

+ * + * + *

Usage example (Maven):

+ *
{@code
+ * // Run all tests in this class:
+ * mvn test -Dtest=WipeConfigurationTest
+ *
+ * // Run a single test method:
+ * mvn test -Dtest=WipeConfigurationTest#testBuilderChaining
+ * }
+ * + * @author Scot P. Floess + * @see WipeConfiguration + * @see WipeConfiguration.Builder + */ class WipeConfigurationTest { + /** + * Verifies that a {@link WipeConfiguration} created via the builder with no explicit + * settings carries the documented defaults: 4 threads, a 10 MB (10,485,760 byte) buffer, + * and confirmation not skipped. + */ @Test void testDefaultValues() { final WipeConfiguration config = new WipeConfiguration.Builder().build(); @@ -31,6 +69,10 @@ void testDefaultValues() { assertFalse(config.isSkipConfirmation()); } + /** + * Confirms that {@link WipeConfiguration.Builder#threadCount(int)} correctly overrides the + * default thread count when a positive value is supplied. + */ @Test void testBuilderThreadCount() { final WipeConfiguration config = new WipeConfiguration.Builder() @@ -40,6 +82,10 @@ void testBuilderThreadCount() { assertEquals(8, config.getThreadCount()); } + /** + * Confirms that {@link WipeConfiguration.Builder#bufferSize(int)} correctly overrides the + * default buffer size when a positive value is supplied. + */ @Test void testBuilderBufferSize() { final WipeConfiguration config = new WipeConfiguration.Builder() @@ -49,6 +95,10 @@ void testBuilderBufferSize() { assertEquals(20 * 1024 * 1024, config.getBufferSize()); } + /** + * Confirms that {@link WipeConfiguration.Builder#skipConfirmation(boolean)} correctly + * overrides the default skip-confirmation flag (which defaults to {@code false}). + */ @Test void testBuilderSkipConfirmation() { final WipeConfiguration config = new WipeConfiguration.Builder() @@ -58,6 +108,10 @@ void testBuilderSkipConfirmation() { assertTrue(config.isSkipConfirmation()); } + /** + * Validates that all builder setters can be chained in a single fluent expression and that + * the resulting {@link WipeConfiguration} reflects every customized value. + */ @Test void testBuilderChaining() { final WipeConfiguration config = new WipeConfiguration.Builder() @@ -71,6 +125,10 @@ void testBuilderChaining() { assertTrue(config.isSkipConfirmation()); } + /** + * Asserts that building a configuration with a thread count of zero throws + * {@link IllegalArgumentException}, since at least one thread is required. + */ @Test void testInvalidThreadCountZero() { final WipeConfiguration.Builder builder = new WipeConfiguration.Builder() @@ -79,6 +137,10 @@ void testInvalidThreadCountZero() { assertThrows(IllegalArgumentException.class, builder::build); } + /** + * Asserts that building a configuration with a negative thread count throws + * {@link IllegalArgumentException}. + */ @Test void testInvalidThreadCountNegative() { final WipeConfiguration.Builder builder = new WipeConfiguration.Builder() @@ -87,6 +149,10 @@ void testInvalidThreadCountNegative() { assertThrows(IllegalArgumentException.class, builder::build); } + /** + * Asserts that building a configuration with a buffer size of zero throws + * {@link IllegalArgumentException}, since a non-empty buffer is required for I/O operations. + */ @Test void testInvalidBufferSizeZero() { final WipeConfiguration.Builder builder = new WipeConfiguration.Builder() @@ -95,6 +161,10 @@ void testInvalidBufferSizeZero() { assertThrows(IllegalArgumentException.class, builder::build); } + /** + * Asserts that building a configuration with a negative buffer size throws + * {@link IllegalArgumentException}. + */ @Test void testInvalidBufferSizeNegative() { final WipeConfiguration.Builder builder = new WipeConfiguration.Builder() @@ -103,6 +173,11 @@ void testInvalidBufferSizeNegative() { assertThrows(IllegalArgumentException.class, builder::build); } + /** + * Verifies that {@link WipeConfiguration#toString()} produces a string containing the + * thread count, buffer size, and skip-confirmation flag, ensuring diagnostic output + * is informative. + */ @Test void testToString() { final WipeConfiguration config = new WipeConfiguration.Builder()