View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.clean;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.RandomAccessFile;
25  import java.nio.channels.FileChannel;
26  import java.nio.channels.FileLock;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.util.Collections;
31  
32  import org.apache.maven.api.plugin.MojoException;
33  import org.apache.maven.api.plugin.testing.Basedir;
34  import org.apache.maven.api.plugin.testing.InjectMojo;
35  import org.apache.maven.api.plugin.testing.MojoTest;
36  import org.junit.jupiter.api.Test;
37  import org.junit.jupiter.api.condition.DisabledOnOs;
38  import org.junit.jupiter.api.condition.EnabledOnOs;
39  import org.junit.jupiter.api.condition.OS;
40  
41  import static org.apache.maven.api.plugin.testing.MojoExtension.getBasedir;
42  import static org.apache.maven.api.plugin.testing.MojoExtension.setVariableValueToObject;
43  import static org.codehaus.plexus.util.IOUtil.copy;
44  import static org.junit.jupiter.api.Assertions.assertFalse;
45  import static org.junit.jupiter.api.Assertions.assertNotNull;
46  import static org.junit.jupiter.api.Assertions.assertThrows;
47  import static org.junit.jupiter.api.Assertions.assertTrue;
48  
49  /**
50   * Test the clean mojo.
51   */
52  @MojoTest
53  public class CleanMojoTest {
54  
55      /**
56       * Tests the simple removal of directories
57       *
58       * @throws Exception in case of an error.
59       */
60      @Test
61      @Basedir("${basedir}/target/test-classes/unit/basic-clean-test")
62      @InjectMojo(goal = "clean")
63      public void testBasicClean(CleanMojo mojo) throws Exception {
64          mojo.execute();
65  
66          assertFalse(checkExists(getBasedir() + "/buildDirectory"), "Directory exists");
67          assertFalse(checkExists(getBasedir() + "/buildOutputDirectory"), "Directory exists");
68          assertFalse(checkExists(getBasedir() + "/buildTestDirectory"), "Directory exists");
69      }
70  
71      /**
72       * Tests the removal of files and nested directories
73       *
74       * @throws Exception in case of an error.
75       */
76      @Test
77      @Basedir("${basedir}/target/test-classes/unit/nested-clean-test")
78      @InjectMojo(goal = "clean")
79      public void testCleanNestedStructure(CleanMojo mojo) throws Exception {
80          mojo.execute();
81  
82          assertFalse(checkExists(getBasedir() + "/target"));
83          assertFalse(checkExists(getBasedir() + "/target/classes"));
84          assertFalse(checkExists(getBasedir() + "/target/test-classes"));
85      }
86  
87      /**
88       * Tests that no exception is thrown when all internal variables are empty and that it doesn't
89       * just remove whats there
90       *
91       * @throws Exception in case of an error.
92       */
93      @Test
94      @Basedir("${basedir}/target/test-classes/unit/empty-clean-test")
95      @InjectMojo(goal = "clean")
96      public void testCleanEmptyDirectories(CleanMojo mojo) throws Exception {
97          mojo.execute();
98  
99          assertTrue(checkExists(getBasedir() + "/testDirectoryStructure"));
100         assertTrue(checkExists(getBasedir() + "/testDirectoryStructure/file.txt"));
101         assertTrue(checkExists(getBasedir() + "/testDirectoryStructure/outputDirectory"));
102         assertTrue(checkExists(getBasedir() + "/testDirectoryStructure/outputDirectory/file.txt"));
103     }
104 
105     /**
106      * Tests the removal of files using fileset
107      *
108      * @throws Exception in case of an error.
109      */
110     @Test
111     @Basedir("${basedir}/target/test-classes/unit/fileset-clean-test")
112     @InjectMojo(goal = "clean")
113     public void testFilesetsClean(CleanMojo mojo) throws Exception {
114         mojo.execute();
115 
116         // fileset 1
117         assertTrue(checkExists(getBasedir() + "/target"));
118         assertTrue(checkExists(getBasedir() + "/target/classes"));
119         assertFalse(checkExists(getBasedir() + "/target/test-classes"));
120         assertTrue(checkExists(getBasedir() + "/target/subdir"));
121         assertFalse(checkExists(getBasedir() + "/target/classes/file.txt"));
122         assertTrue(checkEmpty(getBasedir() + "/target/classes"));
123         assertFalse(checkEmpty(getBasedir() + "/target/subdir"));
124         assertTrue(checkExists(getBasedir() + "/target/subdir/file.txt"));
125 
126         // fileset 2
127         assertTrue(checkExists(getBasedir() + "/" + "buildOutputDirectory"));
128         assertFalse(checkExists(getBasedir() + "/" + "buildOutputDirectory/file.txt"));
129     }
130 
131     /**
132      * Tests the removal of a directory as file
133      *
134      * @throws Exception in case of an error.
135      */
136     @Test
137     @Basedir("${basedir}/target/test-classes/unit/invalid-directory-test")
138     @InjectMojo(goal = "clean")
139     public void testCleanInvalidDirectory(CleanMojo mojo) throws Exception {
140         assertThrows(MojoException.class, mojo::execute, "Should fail to delete a file treated as a directory");
141     }
142 
143     /**
144      * Tests the removal of a missing directory
145      *
146      * @throws Exception in case of an error.
147      */
148     @Test
149     @Basedir("${basedir}/target/test-classes/unit/missing-directory-test")
150     @InjectMojo(goal = "clean")
151     public void testMissingDirectory(CleanMojo mojo) throws Exception {
152         mojo.execute();
153 
154         assertFalse(checkExists(getBasedir() + "/does-not-exist"));
155     }
156 
157     /**
158      * Test the removal of a locked file on Windows systems.
159      * <p>
160      * Note: Unix systems doesn't lock any files.
161      * </p>
162      *
163      * @throws Exception in case of an error.
164      */
165     @Test
166     @EnabledOnOs(OS.WINDOWS)
167     @Basedir("${basedir}/target/test-classes/unit/locked-file-test")
168     @InjectMojo(goal = "clean")
169     public void testCleanLockedFile(CleanMojo mojo) throws Exception {
170         File f = new File(getBasedir(), "buildDirectory/file.txt");
171         try (FileChannel channel = new RandomAccessFile(f, "rw").getChannel();
172                 FileLock ignored = channel.lock()) {
173             assertThrows(MojoException.class, () -> mojo.execute());
174         }
175     }
176 
177     /**
178      * Test the removal of a locked file on Windows systems.
179      * <p>
180      * Note: Unix systems doesn't lock any files.
181      * </p>
182      *
183      * @throws Exception in case of an error.
184      */
185     @Test
186     @EnabledOnOs(OS.WINDOWS)
187     @Basedir("${basedir}/target/test-classes/unit/locked-file-test")
188     @InjectMojo(goal = "clean")
189     public void testCleanLockedFileWithNoError(CleanMojo mojo) throws Exception {
190         setVariableValueToObject(mojo, "failOnError", Boolean.FALSE);
191         assertNotNull(mojo);
192 
193         File f = new File(getBasedir(), "buildDirectory/file.txt");
194         try (FileChannel channel = new RandomAccessFile(f, "rw").getChannel();
195                 FileLock ignored = channel.lock()) {
196             mojo.execute();
197         }
198     }
199 
200     /**
201      * Test the followLink option with windows junctions
202      * @throws Exception
203      */
204     @Test
205     @EnabledOnOs(OS.WINDOWS)
206     public void testFollowLinksWithWindowsJunction() throws Exception {
207         testSymlink((link, target) -> {
208             Process process = new ProcessBuilder()
209                     .directory(link.getParent().toFile())
210                     .command("cmd", "/c", "mklink", "/j", link.getFileName().toString(), target.toString())
211                     .start();
212             process.waitFor();
213             ByteArrayOutputStream baos = new ByteArrayOutputStream();
214             copy(process.getInputStream(), baos);
215             copy(process.getErrorStream(), baos);
216             if (!Files.exists(link)) {
217                 throw new IOException("Unable to create junction: " + baos);
218             }
219         });
220     }
221 
222     /**
223      * Test the followLink option with sym link
224      * @throws Exception
225      */
226     @Test
227     @DisabledOnOs(OS.WINDOWS)
228     public void testFollowLinksWithSymLinkOnPosix() throws Exception {
229         testSymlink((link, target) -> {
230             try {
231                 Files.createSymbolicLink(link, target);
232             } catch (IOException e) {
233                 throw new IOException("Unable to create symbolic link", e);
234             }
235         });
236     }
237 
238     @FunctionalInterface
239     interface LinkCreator {
240         void createLink(Path link, Path target) throws Exception;
241     }
242 
243     private void testSymlink(LinkCreator linkCreator) throws Exception {
244         Cleaner cleaner = new Cleaner(null, null, false, null, null);
245         Path testDir = Paths.get("target/test-classes/unit/test-dir").toAbsolutePath();
246         Path dirWithLnk = testDir.resolve("dir");
247         Path orgDir = testDir.resolve("org-dir");
248         Path jctDir = dirWithLnk.resolve("jct-dir");
249         Path file = orgDir.resolve("file.txt");
250 
251         // create directories, links and file
252         Files.createDirectories(dirWithLnk);
253         Files.createDirectories(orgDir);
254         Files.write(file, Collections.singleton("Hello world"));
255         linkCreator.createLink(jctDir, orgDir);
256         // delete
257         cleaner.delete(dirWithLnk, null, false, true, false);
258         // verify
259         assertTrue(Files.exists(file));
260         assertFalse(Files.exists(jctDir));
261         assertTrue(Files.exists(orgDir));
262         assertFalse(Files.exists(dirWithLnk));
263 
264         // create directories, links and file
265         Files.createDirectories(dirWithLnk);
266         Files.createDirectories(orgDir);
267         Files.write(file, Collections.singleton("Hello world"));
268         linkCreator.createLink(jctDir, orgDir);
269         // delete
270         cleaner.delete(dirWithLnk, null, true, true, false);
271         // verify
272         assertFalse(Files.exists(file));
273         assertFalse(Files.exists(jctDir));
274         assertTrue(Files.exists(orgDir));
275         assertFalse(Files.exists(dirWithLnk));
276     }
277 
278     //    @Provides
279     //    @Singleton
280     //    private Project createProject() {
281     //        ProjectStub project = new ProjectStub();
282     //        project.setGroupId("myGroupId");
283     //        return project;
284     //    }
285 
286     //    @Provides
287     //    @Singleton
288     //    @SuppressWarnings("unused")
289     //    private InternalSession createSession() {
290     //        InternalSession session = SessionStub.getMockSession(LOCAL_REPO);
291     //        Properties props = new Properties();
292     //        props.put("basedir", MojoExtension.getBasedir());
293     //        doReturn(props).when(session).getSystemProperties();
294     //        return session;
295     //    }
296 
297     //    @Provides
298     //    @Singleton
299     //    @SuppressWarnings("unused")
300     //    private MojoExecution createMojoExecution() {
301     //        return new MojoExecutionStub("default-clean", "clean");
302     //    }
303 
304     /**
305      * @param dir a dir or a file
306      * @return true if a file/dir exists, false otherwise
307      */
308     private boolean checkExists(String dir) {
309         return new File(new File(dir).getAbsolutePath()).exists();
310     }
311 
312     /**
313      * @param dir a directory
314      * @return true if a dir is empty, false otherwise
315      */
316     private boolean checkEmpty(String dir) {
317         File[] files = new File(dir).listFiles();
318         return files == null || files.length == 0;
319     }
320 }