View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2;
18  
19  import static org.apache.commons.vfs2.VfsTestUtils.getTestDirectory;
20  import static org.junit.jupiter.api.Assertions.assertNotEquals;
21  
22  import java.io.File;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.Modifier;
26  import java.time.Instant;
27  import java.util.ArrayList;
28  import java.util.Enumeration;
29  import java.util.List;
30  
31  import junit.extensions.TestSetup;
32  import junit.framework.Protectable;
33  import junit.framework.Test;
34  import junit.framework.TestResult;
35  import junit.framework.TestSuite;
36  
37  import org.apache.commons.io.FileUtils;
38  import org.apache.commons.lang3.ArrayUtils;
39  import org.apache.commons.lang3.StringUtils;
40  import org.apache.commons.vfs2.impl.DefaultFileReplicator;
41  import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
42  import org.apache.commons.vfs2.impl.PrivilegedFileReplicator;
43  import org.apache.commons.vfs2.provider.local.DefaultLocalFileProvider;
44  
45  /**
46   * The suite of tests for a file system.
47   */
48  public abstract class AbstractTestSuite extends TestSetup {
49  
50      private static final Thread[] EMPTY_THREAD_ARRAY = {};
51      public static final String WRITE_TESTS_FOLDER = "write-tests";
52      public static final String READ_TESTS_FOLDER = "read-tests";
53  
54      private final ProviderTestConfig providerConfig;
55      private final String prefix;
56      private TestSuite testSuite;
57  
58      private FileObject baseFolder;
59      private FileObject readFolder;
60      private FileObject writeFolder;
61      private DefaultFileSystemManager manager;
62      private File tempDir;
63  
64      private Thread[] startThreadSnapshot;
65      private Thread[] endThreadSnapshot;
66      private final boolean addEmptyDir;
67  
68      protected AbstractTestSuite(final ProviderTestConfig providerConfig, final String prefix, final boolean nested)
69          throws Exception {
70          this(providerConfig, prefix, nested, false);
71      }
72  
73      protected AbstractTestSuite(final ProviderTestConfig providerConfig, final String prefix, final boolean nested,
74          final boolean addEmptyDir) throws Exception {
75          super(new TestSuite());
76          testSuite = (TestSuite) fTest;
77          this.providerConfig = providerConfig;
78          this.prefix = prefix;
79          this.addEmptyDir = addEmptyDir;
80          addBaseTests();
81          if (!nested) {
82              // Add nested tests
83              // TODO - move nested jar and zip tests here
84              // TODO - enable this again
85              // testSuite.addTest( new ProviderTestSuite( new JunctionProviderConfig( providerConfig ), "junction.", true
86              // ));
87          }
88      }
89  
90      /**
91       * Adds base tests - excludes the nested test cases.
92       */
93      protected void addBaseTests() throws Exception {
94      }
95  
96      /**
97       * Adds the tests from a class to this suite. The supplied class must be a subclass of
98       * {@link AbstractProviderTestCase} and have a public a no-args constructor. This method creates an instance of the
99       * supplied class for each public 'testNnnn' method provided by the class.
100      */
101     public void addTests(final Class<?> testClass) throws Exception {
102         // Verify the class
103         if (!AbstractProviderTestCase.class.isAssignableFrom(testClass)) {
104             throw new Exception("Test class " + testClass.getName() + " is not assignable to "
105                 + AbstractProviderTestCase.class.getName());
106         }
107 
108         // Locate the test methods
109         final Method[] methods = testClass.getMethods();
110         for (final Method method2 : methods) {
111             final Method method = method2;
112             if (!method.getName().startsWith("test") || Modifier.isStatic(method.getModifiers())
113                 || method.getReturnType() != Void.TYPE || method.getParameterTypes().length != 0) {
114                 continue;
115             }
116 
117             // Create instance
118             final AbstractProviderTestCase testCase = (AbstractProviderTestCase) testClass.getConstructor().newInstance();
119             testCase.setMethod(method);
120             testCase.setName(prefix + method.getName());
121             testCase.addEmptyDir(addEmptyDir);
122             testSuite.addTest(testCase);
123         }
124     }
125 
126     /**
127      * Asserts that the temp dir is empty or gone.
128      */
129     private void checkTempDir(final String assertMsg) {
130         if (tempDir.exists()) {
131             assertTrue(assertMsg + " (" + tempDir.getAbsolutePath() + ")",
132                 tempDir.isDirectory() && ArrayUtils.isEmpty(tempDir.list()));
133         }
134     }
135 
136     private Thread[] createThreadSnapshot() {
137         ThreadGroup tg = Thread.currentThread().getThreadGroup();
138         if (tg == null) {
139             return EMPTY_THREAD_ARRAY;
140         }
141         while (tg.getParent() != null) {
142             tg = tg.getParent();
143         }
144 
145         final Thread[] snapshot = new Thread[200];
146         tg.enumerate(snapshot, true);
147 
148         return snapshot;
149     }
150 
151     private Thread[] diffThreadSnapshot(final Thread[] startThreadSnapshot, final Thread[] endThreadSnapshot) {
152         final List<Thread> diff = new ArrayList<>(10);
153 
154         nextEnd: for (final Thread element : endThreadSnapshot) {
155             for (final Thread element2 : startThreadSnapshot) {
156                 if (element2 == element) {
157                     continue nextEnd;
158                 }
159             }
160 
161             diff.add(element);
162         }
163 
164         return diff.toArray(EMPTY_THREAD_ARRAY);
165     }
166 
167     private String dumpThreadSnapshot(final Thread[] threadSnapshot) {
168         if (ArrayUtils.isEmpty(threadSnapshot)) {
169             return StringUtils.EMPTY;
170         }
171         final StringBuilder sb = new StringBuilder(256);
172         sb.append("Threads still running (" + threadSnapshot.length + ") at " + Instant.now() + ", live threads:");
173         sb.append(System.lineSeparator());
174 
175         Field threadTargetField = null;
176         try {
177             threadTargetField = Thread.class.getDeclaredField("target");
178             threadTargetField.setAccessible(true);
179         } catch (final Exception e) {
180             System.err.println("Test suite cannot show you a thread snapshot: " + e);
181         }
182 
183         int liveCount = 0;
184         for (int index = 0; index < threadSnapshot.length; index++) {
185             final Thread thread = threadSnapshot[index];
186             if (thread != null && thread.isAlive()) {
187                 liveCount++;
188                 sb.append("\tThread[");
189                 sb.append(index);
190                 sb.append("] ");
191                 sb.append(" ID ");
192                 sb.append(thread.getId());
193                 sb.append(", ");
194                 // prints [name,priority,group]
195                 sb.append(thread);
196                 sb.append(",\t");
197                 sb.append(thread.getState());
198                 sb.append(",\t");
199                 if (!thread.isDaemon()) {
200                     sb.append("non_");
201                 }
202                 sb.append("daemon");
203 
204                 if (threadTargetField != null) {
205                     sb.append(",\t");
206                     try {
207                         final Object threadTarget = threadTargetField.get(thread);
208                         if (threadTarget != null) {
209                             sb.append(threadTarget.getClass().getCanonicalName());
210                         } else {
211                             sb.append("null");
212                         }
213                     } catch (final IllegalAccessException e) {
214                         sb.append("unknown (");
215                         sb.append(e);
216                         sb.append(")");
217                     }
218                 }
219 
220                 sb.append(System.lineSeparator());
221 //              Stream.of(thread.getStackTrace()).forEach(e -> {
222 //                  sb.append('\t');
223 //                  sb.append(e);
224 //                  sb.append(System.lineSeparator());
225 //              });
226             }
227         }
228         return liveCount == 0 ? StringUtils.EMPTY : sb.toString();
229     }
230 
231     @Override
232     public void run(final TestResult result) {
233         final Protectable p = () -> {
234             setUp();
235             basicRun(result);
236             tearDown();
237             validateThreadSnapshot();
238         };
239         result.runProtected(this, p);
240     }
241 
242     @Override
243     protected void setUp() throws Exception {
244         startThreadSnapshot = createThreadSnapshot();
245 
246         // Locate the temp directory, and clean it up
247         tempDir = getTestDirectory("temp");
248         FileUtils.cleanDirectory(tempDir);
249         checkTempDir("Temp dir not empty before test");
250 
251         // Create the file system manager
252         manager = providerConfig.getDefaultFileSystemManager();
253         manager.setFilesCache(providerConfig.getFilesCache());
254 
255         final DefaultFileReplicator replicator = new DefaultFileReplicator(tempDir);
256         manager.setReplicator(new PrivilegedFileReplicator(replicator));
257         manager.setTemporaryFileStore(replicator);
258 
259         providerConfig.prepare(manager);
260 
261         if (!manager.hasProvider("file")) {
262             manager.addProvider("file", new DefaultLocalFileProvider());
263         }
264 
265         manager.init();
266 
267         // Locate the base folders
268         baseFolder = providerConfig.getBaseTestFolder(manager);
269         readFolder = baseFolder.resolveFile(READ_TESTS_FOLDER);
270         writeFolder = baseFolder.resolveFile(WRITE_TESTS_FOLDER);
271 
272         // Make some assumptions about the read folder
273         assertTrue("Folder does not exist: " + readFolder, readFolder.exists());
274         assertNotEquals(readFolder.getName().getPath(), FileName.ROOT_PATH);
275 
276         // Configure the tests
277         final Enumeration<Test> tests = testSuite.tests();
278         if (!tests.hasMoreElements()) {
279             fail("No tests.");
280         }
281         while (tests.hasMoreElements()) {
282             final Test test = tests.nextElement();
283             if (test instanceof AbstractProviderTestCase) {
284                 final AbstractProviderTestCase providerTestCase = (AbstractProviderTestCase) test;
285                 providerTestCase.setConfig(manager, providerConfig, baseFolder, readFolder, writeFolder);
286             }
287         }
288     }
289 
290     @Override
291     protected void tearDown() throws Exception {
292         readFolder.close();
293         writeFolder.close();
294         baseFolder.close();
295 
296         readFolder = null;
297         writeFolder = null;
298         baseFolder = null;
299         testSuite = null;
300 
301         // Suggest to threads (SoftRefFilesCache) to free all files.
302         System.gc();
303         Thread.sleep(1000);
304         System.gc();
305         Thread.sleep(1000);
306         System.gc();
307         Thread.sleep(1000);
308         System.gc();
309         Thread.sleep(1000);
310 
311         manager.freeUnusedResources();
312         manager.close();
313         // Give a chance for any threads to end.
314         Thread.sleep(20);
315 
316         // Make sure temp directory is empty or gone
317         checkTempDir("Temp dir not empty after test");
318         VFS.close();
319     }
320 
321     private void validateThreadSnapshot() {
322         endThreadSnapshot = createThreadSnapshot();
323 
324         final Thread[] diffThreadSnapshot = diffThreadSnapshot(startThreadSnapshot, endThreadSnapshot);
325         if (diffThreadSnapshot.length > 0) {
326             final String message = dumpThreadSnapshot(diffThreadSnapshot);
327             /*
328              * if (providerConfig.checkCleanThreadState()) { // close the manager to do a "not thread safe" release of
329              * all resources // and allow the VM to shutdown manager.close(); fail(message); } else {
330              */
331             System.out.print(message);
332             // }
333         }
334         // System.in.read();
335     }
336 
337 }