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 SetThis 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.
+ *
+ * 0: Success or valid help request1: Errors including validation failures, invalid arguments, or missing required optionsThese 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()