This is an automated email from the ASF dual-hosted git repository.

elharo pushed a commit to branch maven-clean-plugin-3.x
in repository https://gitbox.apache.org/repos/asf/maven-clean-plugin.git


The following commit(s) were added to refs/heads/maven-clean-plugin-3.x by this 
push:
     new cb2127f  [MCLEAN-124] Leverage Files.delete(Path) API to provide more 
accurate reason in case of failure (#60)
cb2127f is described below

commit cb2127f8ddb531edb67de4d9ec99555470b39e81
Author: Peter De Maeyer <peter.de.mae...@gmail.com>
AuthorDate: Sat Nov 9 16:19:42 2024 +0100

    [MCLEAN-124] Leverage Files.delete(Path) API to provide more accurate 
reason in case of failure (#60)
    
    * MCLEAN-124 Leverage Files.delete(Path) API to provide more accurate 
reason in case of failure
    
    * Use Mockito + fix unit tests for Windows
    
    * Renamed variable error -> failure
    
    * Removed the resetting of permissions in tests, relying on JUnit @TempDir 
to take care of permissions issues when clearing the temporary directory
    
    * Fixed typo a -> an
    
    * Simplified the setting of permissions in tests
---
 pom.xml                                            |   6 +
 .../org/apache/maven/plugins/clean/Cleaner.java    |  18 ++-
 .../apache/maven/plugins/clean/CleanerTest.java    | 143 +++++++++++++++++++++
 3 files changed, 163 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 73c3c72..40bb82e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -108,6 +108,12 @@ under the License.
       <artifactId>junit-jupiter-api</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>4.11.0</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-testing</artifactId>
diff --git a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java 
b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
index f5c25d2..52f1e25 100644
--- a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
+++ b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
@@ -273,7 +273,8 @@ class Cleaner {
      * @throws IOException If a file/directory could not be deleted and 
<code>failOnError</code> is <code>true</code>.
      */
     private int delete(File file, boolean failOnError, boolean retryOnError) 
throws IOException {
-        if (!file.delete()) {
+        IOException failure = delete(file);
+        if (failure != null) {
             boolean deleted = false;
 
             if (retryOnError) {
@@ -289,7 +290,7 @@ class Cleaner {
                     } catch (InterruptedException e) {
                         // ignore
                     }
-                    deleted = file.delete() || !file.exists();
+                    deleted = delete(file) == null || !file.exists();
                 }
             } else {
                 deleted = !file.exists();
@@ -297,10 +298,10 @@ class Cleaner {
 
             if (!deleted) {
                 if (failOnError) {
-                    throw new IOException("Failed to delete " + file);
+                    throw new IOException("Failed to delete " + file, failure);
                 } else {
                     if (log.isWarnEnabled()) {
-                        log.warn("Failed to delete " + file);
+                        log.warn("Failed to delete " + file, failure);
                     }
                     return 1;
                 }
@@ -310,6 +311,15 @@ class Cleaner {
         return 0;
     }
 
+    private static IOException delete(File file) {
+        try {
+            Files.delete(file.toPath());
+        } catch (IOException e) {
+            return e;
+        }
+        return null;
+    }
+
     private static class Result {
 
         private int failures;
diff --git a/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java 
b/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
new file mode 100644
index 0000000..e958664
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+package org.apache.maven.plugins.clean;
+
+import java.io.IOException;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Set;
+
+import org.apache.maven.plugin.logging.Log;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+import static java.nio.file.Files.createDirectory;
+import static java.nio.file.Files.createFile;
+import static java.nio.file.Files.exists;
+import static java.nio.file.Files.setPosixFilePermissions;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class CleanerTest {
+
+    private static final boolean POSIX_COMPLIANT =
+            
FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
+
+    private final Log log = mock();
+
+    @Test
+    void deleteSucceedsDeeply(@TempDir Path tempDir) throws Exception {
+        assumeTrue(POSIX_COMPLIANT);
+        final Path basedir = 
createDirectory(tempDir.resolve("target")).toRealPath();
+        final Path file = createFile(basedir.resolve("file"));
+        final Cleaner cleaner = new Cleaner(null, log, false, null, null);
+        cleaner.delete(basedir.toFile(), null, false, true, false);
+        assertFalse(exists(basedir));
+        assertFalse(exists(file));
+    }
+
+    @Test
+    void deleteFailsWithoutRetryWhenNoPermission(@TempDir Path tempDir) throws 
Exception {
+        assumeTrue(POSIX_COMPLIANT);
+        when(log.isWarnEnabled()).thenReturn(true);
+        final Path basedir = 
createDirectory(tempDir.resolve("target")).toRealPath();
+        createFile(basedir.resolve("file"));
+        // Remove the executable flag to prevent directory listing, which will 
result in a DirectoryNotEmptyException.
+        final Set<PosixFilePermission> permissions = 
PosixFilePermissions.fromString("rw-rw-r--");
+        setPosixFilePermissions(basedir, permissions);
+        final Cleaner cleaner = new Cleaner(null, log, false, null, null);
+        final IOException exception =
+                assertThrows(IOException.class, () -> 
cleaner.delete(basedir.toFile(), null, false, true, false));
+        verify(log, never()).warn(any(CharSequence.class), 
any(Throwable.class));
+        assertEquals("Failed to delete " + basedir, exception.getMessage());
+        final DirectoryNotEmptyException cause =
+                assertInstanceOf(DirectoryNotEmptyException.class, 
exception.getCause());
+        assertEquals(basedir.toString(), cause.getMessage());
+    }
+
+    @Test
+    void deleteFailsAfterRetryWhenNoPermission(@TempDir Path tempDir) throws 
Exception {
+        assumeTrue(POSIX_COMPLIANT);
+        final Path basedir = 
createDirectory(tempDir.resolve("target")).toRealPath();
+        createFile(basedir.resolve("file"));
+        // Remove the executable flag to prevent directory listing, which will 
result in a DirectoryNotEmptyException.
+        final Set<PosixFilePermission> permissions = 
PosixFilePermissions.fromString("rw-rw-r--");
+        setPosixFilePermissions(basedir, permissions);
+        final Cleaner cleaner = new Cleaner(null, log, false, null, null);
+        final IOException exception =
+                assertThrows(IOException.class, () -> 
cleaner.delete(basedir.toFile(), null, false, true, true));
+        assertEquals("Failed to delete " + basedir, exception.getMessage());
+        final DirectoryNotEmptyException cause =
+                assertInstanceOf(DirectoryNotEmptyException.class, 
exception.getCause());
+        assertEquals(basedir.toString(), cause.getMessage());
+    }
+
+    @Test
+    void deleteLogsWarningWithoutRetryWhenNoPermission(@TempDir Path tempDir) 
throws Exception {
+        assumeTrue(POSIX_COMPLIANT);
+        when(log.isWarnEnabled()).thenReturn(true);
+        final Path basedir = 
createDirectory(tempDir.resolve("target")).toRealPath();
+        final Path file = createFile(basedir.resolve("file"));
+        // Remove the writable flag to prevent deletion of the file, which 
will result in an AccessDeniedException.
+        final Set<PosixFilePermission> permissions = 
PosixFilePermissions.fromString("r-xr-xr-x");
+        setPosixFilePermissions(basedir, permissions);
+        final Cleaner cleaner = new Cleaner(null, log, false, null, null);
+        assertDoesNotThrow(() -> cleaner.delete(basedir.toFile(), null, false, 
false, false));
+        verify(log, times(2)).warn(any(CharSequence.class), 
any(Throwable.class));
+        InOrder inOrder = inOrder(log);
+        ArgumentCaptor<AccessDeniedException> cause1 = 
ArgumentCaptor.forClass(AccessDeniedException.class);
+        inOrder.verify(log).warn(eq("Failed to delete " + file), 
cause1.capture());
+        assertEquals(file.toString(), cause1.getValue().getMessage());
+        ArgumentCaptor<DirectoryNotEmptyException> cause2 = 
ArgumentCaptor.forClass(DirectoryNotEmptyException.class);
+        inOrder.verify(log).warn(eq("Failed to delete " + basedir), 
cause2.capture());
+        assertEquals(basedir.toString(), cause2.getValue().getMessage());
+    }
+
+    @Test
+    void deleteDoesNotLogAnythingWhenNoPermissionAndWarnDisabled(@TempDir Path 
tempDir) throws Exception {
+        assumeTrue(POSIX_COMPLIANT);
+        when(log.isWarnEnabled()).thenReturn(false);
+        final Path basedir = 
createDirectory(tempDir.resolve("target")).toRealPath();
+        createFile(basedir.resolve("file"));
+        // Remove the writable flag to prevent deletion of the file, which 
will result in an AccessDeniedException.
+        final Set<PosixFilePermission> permissions = 
PosixFilePermissions.fromString("r-xr-xr-x");
+        setPosixFilePermissions(basedir, permissions);
+        final Cleaner cleaner = new Cleaner(null, log, false, null, null);
+        assertDoesNotThrow(() -> cleaner.delete(basedir.toFile(), null, false, 
false, false));
+        verify(log, never()).warn(any(CharSequence.class), 
any(Throwable.class));
+    }
+}

Reply via email to