From e2812c171c38cbffc8371a18259d536b350331fa Mon Sep 17 00:00:00 2001 From: Flossy Date: Sun, 7 Jun 2026 05:03:48 -0400 Subject: [PATCH 1/2] Add comprehensive method-level Javadoc to test classes Enhanced test documentation by adding detailed method-level Javadoc comments to CleanDiskTest, FileWorkerTest, and WipeConfigurationTest. All test methods now include clear descriptions of what they verify, improving code maintainability and making it easier for developers to understand test coverage and intent. Co-Authored-By: Claude Sonnet 4.5 --- .../org/flossware/diskwipe/CleanDiskTest.java | 187 ++++++++++++++++++ .../flossware/diskwipe/FileWorkerTest.java | 118 +++++++++++ .../diskwipe/WipeConfigurationTest.java | 75 +++++++ 3 files changed, 380 insertions(+) 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() From 736fe31680afd01e11552429774bc097d217f026 Mon Sep 17 00:00:00 2001 From: Flossy Date: Sun, 7 Jun 2026 06:01:34 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9A=20Add=20missing=20documentatio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive Javadoc documentation for: - DANGEROUS_PATHS constant in CleanDisk.java with safety warnings and usage examples - DEFAULT_THREAD_COUNT constant in WipeConfiguration.java with usage examples - toString() method in WipeConfiguration.java with output format details Also update .gitignore to exclude log files. Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 2 + .../org/flossware/diskwipe/CleanDisk.java | 27 ++++++++++++ .../flossware/diskwipe/WipeConfiguration.java | 43 +++++++++++++++++++ 3 files changed, 72 insertions(+) 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}",