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.vfs.test;
18  
19  import junit.extensions.TestSetup;
20  import junit.framework.Test;
21  import junit.framework.TestSuite;
22  import org.apache.commons.AbstractVfsTestCase;
23  import org.apache.commons.vfs.FileName;
24  import org.apache.commons.vfs.FileObject;
25  import org.apache.commons.vfs.impl.DefaultFileReplicator;
26  import org.apache.commons.vfs.impl.DefaultFileSystemManager;
27  import org.apache.commons.vfs.impl.PrivilegedFileReplicator;
28  import org.apache.commons.vfs.provider.local.DefaultLocalFileProvider;
29  
30  import java.io.File;
31  import java.lang.reflect.Field;
32  import java.lang.reflect.Method;
33  import java.lang.reflect.Modifier;
34  import java.util.ArrayList;
35  import java.util.Enumeration;
36  import java.util.List;
37  
38  /***
39   * The suite of tests for a file system.
40   *
41   * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
42   * @author Gary D. Gregory
43   * @version $Id: AbstractTestSuite.java 480428 2006-11-29 06:15:24Z bayard $
44   */
45  public class AbstractTestSuite
46      extends TestSetup
47  {
48      private final ProviderTestConfig providerConfig;
49      private final String prefix;
50      private TestSuite testSuite;
51  
52      private FileObject baseFolder;
53      private FileObject readFolder;
54      private FileObject writeFolder;
55      private DefaultFileSystemManager manager;
56      private File tempDir;
57  
58      private Thread[] startThreadSnapshot;
59      private Thread[] endThreadSnapshot;
60  
61      /***
62       * Adds the tests for a file system to this suite.
63       */
64      public AbstractTestSuite(final ProviderTestConfig providerConfig) throws Exception
65      {
66          this(providerConfig, "", false);
67      }
68  
69      protected AbstractTestSuite(final ProviderTestConfig providerConfig,
70                                  final String prefix,
71                                  final boolean nested)
72          throws Exception
73      {
74          super(new TestSuite());
75          testSuite = (TestSuite) fTest;
76          this.providerConfig = providerConfig;
77          this.prefix = prefix;
78          addBaseTests();
79          if (!nested)
80          {
81              // Add nested tests
82              // TODO - move nested jar and zip tests here
83              // TODO - enable this again
84              //testSuite.addTest( new ProviderTestSuite( new JunctionProviderConfig( providerConfig ), "junction.", true ));
85          }
86      }
87  
88      /***
89       * Adds base tests - excludes the nested test cases.
90       */
91      protected void addBaseTests() throws Exception
92      {
93      }
94  
95      /***
96       * Adds the tests from a class to this suite.  The supplied class must be
97       * a subclass of {@link AbstractProviderTestCase} and have a public a
98       * no-args constructor.  This method creates an instance of the supplied
99       * class for each public 'testNnnn' method provided by the class.
100      */
101     public void addTests(final Class testClass) throws Exception
102     {
103         // Verify the class
104         if (!AbstractProviderTestCase.class.isAssignableFrom(testClass))
105         {
106             throw new Exception("Test class " + testClass.getName() + " is not assignable to " + AbstractProviderTestCase.class.getName());
107         }
108 
109         // Locate the test methods
110         final Method[] methods = testClass.getMethods();
111         for (int i = 0; i < methods.length; i++)
112         {
113             final Method method = methods[i];
114             if (!method.getName().startsWith("test")
115                 || Modifier.isStatic(method.getModifiers())
116                 || method.getReturnType() != Void.TYPE
117                 || method.getParameterTypes().length != 0)
118             {
119                 continue;
120             }
121 
122             // Create instance
123             final AbstractProviderTestCase testCase = (AbstractProviderTestCase) testClass.newInstance();
124             testCase.setMethod(method);
125             testCase.setName(prefix + method.getName());
126             testSuite.addTest(testCase);
127         }
128     }
129 
130     protected void setUp() throws Exception
131     {
132         startThreadSnapshot = createThreadSnapshot();
133 
134         // Locate the temp directory, and clean it up
135         tempDir = AbstractVfsTestCase.getTestDirectory("temp");
136         checkTempDir("Temp dir not empty before test");
137 
138         // Create the file system manager
139         manager = new DefaultFileSystemManager();
140         manager.setFilesCache(providerConfig.getFilesCache());
141 
142         final DefaultFileReplicator replicator = new DefaultFileReplicator(tempDir);
143         manager.setReplicator(new PrivilegedFileReplicator(replicator));
144         manager.setTemporaryFileStore(replicator);
145 
146         providerConfig.prepare(manager);
147 
148         if (!manager.hasProvider("file"))
149         {
150             manager.addProvider("file", new DefaultLocalFileProvider());
151         }
152 
153         manager.init();
154 
155         // Locate the base folders
156         baseFolder = providerConfig.getBaseTestFolder(manager);
157         readFolder = baseFolder.resolveFile("read-tests");
158         writeFolder = baseFolder.resolveFile("write-tests");
159 
160         // Make some assumptions about the read folder
161         assertTrue("Folder does not exist: " + readFolder, readFolder.exists());
162         assertFalse(readFolder.getName().getPath().equals(FileName.ROOT_PATH));
163 
164         // Configure the tests
165         final Enumeration tests = testSuite.tests();
166         while (tests.hasMoreElements())
167         {
168             final Test test = (Test) tests.nextElement();
169             if (test instanceof AbstractProviderTestCase)
170             {
171                 final AbstractProviderTestCase providerTestCase = (AbstractProviderTestCase) test;
172                 providerTestCase.setConfig(manager, providerConfig, baseFolder, readFolder, writeFolder);
173             }
174         }
175     }
176 
177     protected void tearDown() throws Exception
178     {
179         readFolder.close();
180         writeFolder.close();
181         baseFolder.close();
182 
183         readFolder = null;
184         writeFolder = null;
185         baseFolder = null;
186         testSuite = null;
187         fTest = null;
188 
189         // force the SoftRefFilesChache to free all files
190         System.err.println(".");
191         System.gc();
192         Thread.sleep(1000);
193         System.err.println(".");
194         System.gc();
195         Thread.sleep(1000);
196         System.err.println(".");
197         System.gc();
198         Thread.sleep(1000);
199         System.err.println(".");
200         System.gc();
201         Thread.sleep(1000);
202 
203         manager.freeUnusedResources();
204         endThreadSnapshot = createThreadSnapshot();
205 
206         Thread[] diffThreadSnapshot = diffThreadSnapshot(startThreadSnapshot, endThreadSnapshot);
207         if (diffThreadSnapshot.length > 0)
208         {
209             String message = dumpThreadSnapshot(diffThreadSnapshot);
210             /*
211             if (providerConfig.checkCleanThreadState())
212             {
213                 // close the manager to do a "not thread safe" release of all resources
214                 // and allow the vm to shutdown
215                 manager.close();
216                 fail(message);
217             }
218             else
219             {
220             */
221             System.out.println(message);
222             // }
223         }
224         // System.in.read();
225 
226         manager.close();
227 
228         // Make sure temp directory is empty or gone
229         checkTempDir("Temp dir not empty after test");
230     }
231 
232     /***
233      * Asserts that the temp dir is empty or gone.
234      */
235     private void checkTempDir(final String assertMsg)
236     {
237         if (tempDir.exists())
238         {
239             assertTrue(assertMsg + " (" + tempDir.getAbsolutePath() + ")", tempDir.isDirectory() && tempDir.list().length == 0);
240         }
241     }
242 
243     private String dumpThreadSnapshot(Thread[] threadSnapshot)
244     {
245         StringBuffer sb = new StringBuffer(256);
246         sb.append("created threads still running:\n");
247 
248         Field threadTargetField = null;
249         try
250         {
251             threadTargetField = Thread.class.getDeclaredField("target");
252             threadTargetField.setAccessible(true);
253         }
254         catch (NoSuchFieldException e)
255         {
256             ;
257         }
258 
259         for (int iter = 0; iter < threadSnapshot.length; iter++)
260         {
261             Thread thread = threadSnapshot[iter];
262             if (thread == null)
263             {
264                 continue;
265             }
266 
267             sb.append("#");
268             sb.append(iter + 1);
269             sb.append(": ");
270             sb.append(thread.getThreadGroup().getName());
271             sb.append("\t");
272             sb.append(thread.getName());
273             sb.append("\t");
274             if (thread.isDaemon())
275             {
276                 sb.append("daemon");
277             }
278             else
279             {
280                 sb.append("not_a_daemon");
281             }
282 
283             if (threadTargetField != null)
284             {
285                 sb.append("\t");
286                 try
287                 {
288                     Object threadTarget = threadTargetField.get(thread);
289                     if (threadTarget != null)
290                     {
291                         sb.append(threadTarget.getClass());
292                     }
293                     else
294                     {
295                         sb.append("null");
296                     }
297                 }
298                 catch (IllegalAccessException e)
299                 {
300                     sb.append("unknown class");
301                 }
302             }
303 
304             sb.append("\n");
305         }
306 
307         return sb.toString();
308     }
309 
310     private Thread[] diffThreadSnapshot(Thread[] startThreadSnapshot, Thread[] endThreadSnapshot)
311     {
312         List diff = new ArrayList(10);
313 
314         nextEnd: for (int iterEnd = 0; iterEnd < endThreadSnapshot.length; iterEnd++)
315         {
316             nextStart: for (int iterStart = 0; iterStart < startThreadSnapshot.length; iterStart++)
317             {
318                 if (startThreadSnapshot[iterStart] == endThreadSnapshot[iterEnd])
319                 {
320                     continue nextEnd;
321                 }
322             }
323 
324             diff.add(endThreadSnapshot[iterEnd]);
325         }
326 
327         Thread ret[] = new Thread[diff.size()];
328         diff.toArray(ret);
329         return ret;
330     }
331 
332     private Thread[] createThreadSnapshot()
333     {
334         ThreadGroup tg = Thread.currentThread().getThreadGroup();
335         while (tg.getParent() != null)
336         {
337             tg = tg.getParent();
338         }
339 
340         Thread snapshot[] = new Thread[200];
341         tg.enumerate(snapshot, true);
342 
343         return snapshot;
344     }
345 }